// Copyright 2022 Huawei Cloud Computing Technology Co., Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

class AESGCMCrypto {
    static _base() {
        let crypto = window.crypto || window.msCrypto;
        let subtle = crypto && (crypto.subtle || crypto.webkitSubtle || crypto.msSubtle);
        return {crypto, subtle};
    }

    static isSupport() {
        let {subtle} = AESGCMCrypto._base();
        return subtle;
    }

    static getRandomValue() {
        let crypto = window.crypto || window.msCrypto;
        return crypto.getRandomValues(new Uint8Array(1)) * 0.001;
    }

    constructor() {
        this._init();
    }

    _init() {
        Object.assign(this, AESGCMCrypto._base());
        if (!this.subtle) {
            throw new Error('请使用HTTPS协议。');
        }

        if (!this.crypto) {
            throw new Error('浏览器版本太低, 建议使用最新的chrome浏览器。');
        }
    }

    buf2hex(buf) {
        return Array.prototype.map.call(new Uint8Array(buf), x => x.toString(16).padStart(2, '0')).join('');
    }

    hex2buf(str) {
        let len = str.length % 2 + str.length;
        let buf = new Uint8Array(len / 2);
        str = str.padStart(len, '0');
        for (let i = 0; i < len; i += 2) {
            buf[i / 2] = parseInt(str.substr(i, 2), 16);
        }

        return buf;
    }

    str2buf(str) {
        let buf = new Uint8Array(str.length);
        for (let i = 0, strLen = str.length; i < strLen; i++) {
            buf[i] = str.charCodeAt(i);
        }

        return buf;
    }

    iv(len = 32) {
        let iv = '';
        do {
            iv += AESGCMCrypto.getRandomValue().toString(16).replace('.', '');
        } while (iv.length < len);

        return iv.substr(0, len);
    }

    abstract(text) {
        let self = this;
        let subtle = this.subtle;
        return new Promise((resolve, reject) => {
            subtle.digest('SHA-256', self.str2buf(text)).then(buf => {
                resolve(self.buf2hex(buf));
            }, err => {
                reject(err);
            });
        });
    }

    encrypt(text, key, iv) {
        let self = this;
        let subtle = this.subtle;
        return new Promise((resolve, reject) => {
            subtle.importKey(
                'raw',
                self.hex2buf(key),
                {name: 'AES-GCM'},
                false,
                ['encrypt', 'decrypt']
            ).then(cryptoKey => {
                subtle.encrypt(
                    {
                        name: 'AES-GCM',
                        iv: self.hex2buf(iv)
                    }, cryptoKey, self.str2buf(text)
                ).then(buf => {
                    resolve(self.buf2hex(buf));
                }, err => {
                    reject(err);
                });
            }, err => {
                reject(err);
            });
        }); 
    }
}

export default AESGCMCrypto;
